-
Notifications
You must be signed in to change notification settings - Fork 729
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JSON formatter #377
JSON formatter #377
Conversation
Oh and I just started today with the tracing codebase, so I might not know every detail how this code should be formed. :) |
Eugh, sorry about the duplicate comments. Computers! |
Understood how the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for making the changes I requested! I left some notes on the structure of the serialized output.
tracing-subscriber/src/fmt/format.rs
Outdated
serializer.serialize_entry("timestamp", ×tamp)?; | ||
serializer.serialize_entry("level", &meta.level().as_serde())?; | ||
|
||
ctx.with_current(|(_, span)| serializer.serialize_entry("span", span.name())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this will only serialize the name of the immediate parent span. it seems like it might be good to serialize the span's fields as well — note that if we don't also serialize fields, the JSON formatter will output strictly less information than the text formatter, which does include the parent span's fields.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current Span
implementation in tracing-subscriber
formats fields in a format suitable for plain test logs, but looks kind of bad in JSON. See the test example here:
pimeys@b143824#diff-12298a6463a49769f9b40d8e8b01e754R817
I couldn't really track where and why do we stringify the fields in this way. Would be nicer to have a struct that implements Display
and AsSerde
to the both formats.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fields are formatted by a type implementing the FormatFields
trait:
tracing/tracing-subscriber/src/fmt/format.rs
Lines 49 to 64 in 06daa95
/// A type that can format a [set of fields] to a `fmt::Write`. | |
/// | |
/// `FormatFields` is primarily used in the context of [`FmtSubscriber`]. Each | |
/// time a span or event with fields is recorded, the subscriber will format | |
/// those fields with its associated `FormatFields` implementation. | |
/// | |
/// [set of fields]: ../field/trait.RecordFields.html | |
/// [`FmtSubscriber`]: ../fmt/struct.Subscriber.html | |
pub trait FormatFields<'writer> { | |
/// Format the provided `fields` to the provided `writer`, returning a result. | |
fn format_fields<R: RecordFields>( | |
&self, | |
writer: &'writer mut dyn fmt::Write, | |
fields: R, | |
) -> fmt::Result; | |
} |
The
FormatFields
implementation used can be set by the Builder
here:tracing/tracing-subscriber/src/fmt/mod.rs
Lines 402 to 433 in 06daa95
/// Sets the Visitor that the subscriber being built will use to record | |
/// fields. | |
/// | |
/// For example: | |
/// ```rust | |
/// use tracing_subscriber::fmt::{Subscriber, format}; | |
/// use tracing_subscriber::prelude::*; | |
/// | |
/// let formatter = | |
/// // Construct a custom formatter for `Debug` fields | |
/// format::debug_fn(|writer, field, value| write!(writer, "{}: {:?}", field, value)) | |
/// // Use the `tracing_subscriber::MakeFmtExt` trait to wrap the | |
/// // formatter so that a delimiter is added between fields. | |
/// .delimited(", "); | |
/// | |
/// let subscriber = Subscriber::builder() | |
/// .fmt_fields(formatter) | |
/// .finish(); | |
/// # drop(subscriber) | |
/// ``` | |
pub fn fmt_fields<N2>(self, fmt_fields: N2) -> Builder<N2, E, F, W> | |
where | |
N2: for<'writer> FormatFields<'writer> + 'static, | |
{ | |
Builder { | |
fmt_fields: fmt_fields.into(), | |
fmt_event: self.fmt_event, | |
filter: self.filter, | |
settings: self.settings, | |
make_writer: self.make_writer, | |
} | |
} |
FormatFields
is also implemented for any MakeVisitor
implementation:tracing/tracing-subscriber/src/fmt/format.rs
Lines 285 to 299 in 06daa95
impl<'writer, M> FormatFields<'writer> for M | |
where | |
M: MakeOutput<&'writer mut dyn fmt::Write, fmt::Result>, | |
M::Visitor: VisitFmt + VisitOutput<fmt::Result>, | |
{ | |
fn format_fields<R: RecordFields>( | |
&self, | |
writer: &'writer mut dyn fmt::Write, | |
fields: R, | |
) -> fmt::Result { | |
let mut v = self.make_visitor(writer); | |
fields.record(&mut v); | |
v.finish() | |
} | |
} |
So, we could override the default field formatter to record the fields using a Serde
visitor. Although the formatted representation would be stored as a string, we could then interpolate that string into the output, and the fields would be formatted as JSON.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is done now but rendering the fields in an escaped form due to serde not really helping us here. We decided in Discord to leave it how it is for now and come back later to fix the architecture to make field formatting better for different formats.
# only required by the json feature | ||
serde_json = { version = "1.0", optional = true } | ||
serde = { version = "1.0", optional = true } | ||
tracing-serde = { path = "../tracing-serde", optional = true } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll need to publish a tracing-serde
release to crates.io before we can publish the changes in this branch — we can't publish a crate that depends on an unpublished crate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True. Do you have a plan when we want this to happen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When these changes land, I can take care of that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks really close. Beyond moving the WriteAdaptor
type out of tracing-serde
, I'd be happy to merge this as-is; most of my comments are for follow-up work.
When this merges, it would be good to open new issues to track those follow-ups.
serializer.serialize_entry("timestamp", ×tamp)?; | ||
serializer.serialize_entry("level", &meta.level().as_serde())?; | ||
|
||
ctx.with_current(|(_, span)| serializer.serialize_entry("span", &span)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This serializes only the current span in which the event occurred, rather than the span and all its parents (the way the Format<Full, ...>
formatter does. We might want to have separate "full" and "compact" JSON formatters as well (though I'm fine with adding that in a follow-up).
) | ||
.get_matches(); | ||
|
||
match value_t!(matches, "log_format", LogFormat).unwrap_or(LogFormat::Plain) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would still really like it if all of this was factored out of main
into a separate function, but it's not a blocker. It would be fine if that function called set_global_default
so that we don't have to handle return type issues...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me! Thanks @pimeys for all your hard work — I know this took a while to get through.
We can ignore the nightly CI failure for now; it's due to a nightly change that broke our CI scripts and is unrelated. I'm going to go ahead and merge this when the other builds are done, and open tickets for some of the follow-up work we discussed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hawkw's review is a more fine-grained superset of mine; I'm happy merging as is!
This reverts commit 3e4c8d9.
Co-Authored-By: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
Signed-off-by: Eliza Weisman <[email protected]>
0.2.0 (February 4, 2020) Breaking Changes: - **fmt**: Renamed `Context` to `FmtContext` (#420, #425) - **fmt**: Renamed `Builder` to `SubscriberBuilder` (#420) - **filter**: Removed `Filter`. Use `EnvFilter` instead (#434) Added: - **registry**: `Registry`, a reusable span store that `Layer`s can use a high-performance, in-memory store. (#420, #425, #432, #433, #435) - **registry**: Added `LookupSpan` trait, implemented by `Subscriber`s to expose stored span data to `Layer`s (#420) - **fmt**: Added `fmt::Layer`, to allow composing log formatting with other `Layer`s (#420) - **fmt**: Added support for JSON field and event formatting (#377, #415) - **filter**: Documentation for filtering directives (#554) Changed: - **fmt**: Renamed `Context` to `FmtContext` (#420, #425) (BREAKING) - **fmt**: Renamed `Builder` to `SubscriberBuilder` (#420) (BREAKING) - **fmt**: Reimplemented `fmt::Subscriber` in terms of the `Registry` and `Layer`s (#420) Removed: - **filter**: Removed `Filter`. Use `EnvFilter` instead (#434) (BREAKING) Fixed: - **fmt**: Fixed memory leaks in the slab used to store per-span data (3c35048) - **fmt**: `fmt::SubscriberBuilder::init` not setting up `log` compatibility (#489) - **fmt**: Spans closed by a child span closing not also closing _their_ parents (#514) - **Layer**: Fixed `Layered` subscribers failing to downcast to their own type (#549) - **Layer**: Fixed `Layer::downcast_ref` returning invalid references (#454) Signed-off-by: Eliza Weisman <[email protected]>
# 0.2.0 (February 4, 2020) ### Breaking Changes - **fmt**: Renamed `Context` to `FmtContext` (#420, #425) - **fmt**: Renamed `Builder` to `SubscriberBuilder` (#420) - **filter**: Removed `Filter`. Use `EnvFilter` instead (#434) ### Added - **registry**: `Registry`, a `Subscriber` implementation that `Layer`s can use as a high-performance, in-memory span store. (#420, #425, #432, #433, #435) - **registry**: Added `LookupSpan` trait, implemented by `Subscriber`s to expose stored span data to `Layer`s (#420) - **fmt**: Added `fmt::Layer`, to allow composing log formatting with other `Layer`s - **fmt**: Added support for JSON field and event formatting (#377, #415) - **filter**: Documentation for filtering directives (#554) ### Changed - **fmt**: Renamed `Context` to `FmtContext` (#420, #425) (BREAKING) - **fmt**: Renamed `Builder` to `SubscriberBuilder` (#420) (BREAKING) - **fmt**: Reimplemented `fmt::Subscriber` in terms of the `Registry` and `Layer`s (#420) ### Removed - **filter**: Removed `Filter`. Use `EnvFilter` instead (#434) (BREAKING) ### Fixed - **fmt**: Fixed memory leaks in the slab used to store per-span data (3c35048) - **fmt**: `fmt::SubscriberBuilder::init` not setting up `log` compatibility (#489) - **fmt**: Spans closed by a child span closing not also closing _their_ parents (#514) - **Layer**: Fixed `Layered` subscribers failing to downcast to their own type (#549) - **Layer**: Fixed `Layer::downcast_ref` returning invalid references (#454) Fixes #515 Fixes #458 Signed-off-by: Eliza Weisman <[email protected]>
Motivation
A formatter for JSON output.
Solution
Enable
json
feature fromtracing-subscriber
crate. Instantiate a new formatter with JSON output: